package com.inetools.gwetons.client;

import java.util.ArrayList;
import java.util.LinkedHashMap;

import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JavaScriptException;
import com.google.gwt.dom.client.BodyElement;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.Style;
import com.google.gwt.event.logical.shared.ResizeEvent;
import com.google.gwt.event.logical.shared.ResizeHandler;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.Window.ScrollEvent;
import com.google.gwt.user.client.Window.ScrollHandler;
import com.google.gwt.user.client.ui.RootPanel;
import com.inetools.gwetons.client.impl.CalcFixPositionCoordinateImpl;

//TODO need to confirm the case of using in iframe
/* 
 * New scale origin
 * 一番厄介なのは case that minimum width CSS style rule is specified
*/
public class FixPositionInClientWindow {
	protected static FixPositionInClientWindow _instance = new FixPositionInClientWindow();
	
/*
	protected boolean resized = false;
	public boolean isResized() {
		return resized;
	}
	protected void setResized(boolean resized) {
		this.resized = resized;
	}
*/

	protected void dumpCoordiateData() {
		BodyElement bodyElement = BodyElement.as( RootPanel.getBodyElement());
GWT.log( "bodyElement.getOffsetWidth() = " + bodyElement.getOffsetWidth() + ", bodyElement.getOffsetHeight() = " + bodyElement.getOffsetHeight(), null);			
GWT.log( "bodyElement.getScrollWidth() = " + bodyElement.getScrollWidth() + ", bodyElement.getScrollHeight() = " + bodyElement.getScrollHeight(), null);					
GWT.log( "bodyElement.getClientWidth() = " + bodyElement.getClientWidth() + ", bodyElement.getClientHeight() = " + bodyElement.getClientHeight(), null);					
GWT.log( "Window.getClientWidth() = " + Window.getClientWidth() + ", Window.getClientHeight() = " + Window.getClientHeight(), null);			
GWT.log( "bodyElement.getScrollLeft() = " + bodyElement.getScrollLeft() + ", bodyElement.getScrollTop() = " + bodyElement.getScrollTop(), null);			
GWT.log( "Window.getScrollLeft() = " + Window.getScrollLeft() + ", Window.getScrollTop() = " + Window.getScrollTop(), null);			
	}
	
	protected class WindowScrollHandler implements ScrollHandler {
		@Override
		public void onWindowScroll(ScrollEvent event) {
			BodyElement bodyElement = BodyElement.as( RootPanel.getBodyElement());
			int bodyWidth = bodyElement.getOffsetWidth();
			int bodyHeight = bodyElement.getOffsetHeight();
			int clientWidth = Window.getClientWidth();
			int clientHeight = Window.getClientHeight();
			int scrollLeft = event.getScrollLeft();
			int scrollTop = event.getScrollTop();
			
			for( Element element : _instance.elementLinkedHashMap.keySet()) {
				CoordinateSet coordinateSet = _instance.elementLinkedHashMap.get( element);
				if ( "fixed".equalsIgnoreCase( coordinateSet.getElementStyle().getProperty( "position"))) 
					continue;
/*
				int absoluteLeft = element.getAbsoluteLeft();
				int absoluteTop = element.getAbsoluteTop();
//				int scrollLeft = event.getScrollLeft();
				int scrollLeft = Window.getScrollLeft();
//				int scrollTop = event.getScrollTop();
				int scrollTop = Window.getScrollTop();
*/
				
				if ( coordinateSet.getFormulaSet().getLeftFormula() != null) {
					Integer left 
					= coordinateSet.calcLeftFormula(
							bodyWidth, bodyHeight, clientWidth, clientHeight, scrollLeft, scrollTop, 
							_instance);
					if ( left != null) {
						calcFixPositionCoordinateImpl.setPosition( element, AxisOrigin.Left, left);
					}
				}
				if ( coordinateSet.getFormulaSet().getRightFormula() != null) {
					Integer right 
					= coordinateSet.calcRightFormula(
							bodyWidth, bodyHeight, clientWidth, clientHeight, scrollLeft, scrollTop, 
							_instance);
					if ( right != null) {
						calcFixPositionCoordinateImpl.setPosition( element, AxisOrigin.Right, right);					
					}
				}
				if ( coordinateSet.getFormulaSet().getTopFormula() != null) {
					Integer top 
					= coordinateSet.calcTopFormula(
							bodyWidth, bodyHeight, clientWidth, clientHeight, scrollLeft, scrollTop, 
							_instance);
					if ( top != null) {
						calcFixPositionCoordinateImpl.setPosition( element, AxisOrigin.Top, top);					
					}
				}
				if ( coordinateSet.getFormulaSet().getBottomFormula() != null) {
					Integer bottom 
					= coordinateSet.calcBottomFormula(
							bodyWidth, bodyHeight, clientWidth, clientHeight, scrollLeft, scrollTop, 
							_instance);
					if ( bottom != null) {
						calcFixPositionCoordinateImpl.setPosition( element, AxisOrigin.Bottom, bottom);					
					}
				}
			} // for
		} // public void onWindowScroll(ScrollEvent event)
	} // protected class WindowScrollHandler implements ScrollHandler
	
/*
	protected class CalcFixPositionCoordinateImpl {
		
		protected void setFixedStyle( Style elementStyle, Integer x, Integer y) {
			elementStyle.setProperty( "position", "fixed");
			
			elementStyle.setPropertyPx( "left", x);
			elementStyle.setPropertyPx( "top", y);
		}
	}
	protected class CalcFixPositionCoordinateImplIE6 extends CalcFixPositionCoordinateImpl {
		@Override
		protected void setFixedStyle( Style elementStyle, Integer x, Integer y) {
			elementStyle.setProperty( "position", "absolute");
			
			elementStyle.setPropertyPx( "left", x + Window.getScrollLeft());
			elementStyle.setPropertyPx( "top", y + Window.getScrollTop());
		}
	}
*/	
	protected class WindowResizeHandler implements ResizeHandler {
		@Override
		public void onResize(ResizeEvent event) {
dumpCoordiateData();
			BodyElement bodyElement = BodyElement.as( RootPanel.getBodyElement());
			int bodyWidth = bodyElement.getOffsetWidth();
			int bodyHeight = bodyElement.getOffsetHeight();
			int clientWidth = Window.getClientWidth();
			int clientHeight = Window.getClientHeight();
			int windowScrollLeft = Window.getScrollLeft();
			int windowScrollTop = Window.getScrollTop();
			
			for( Element element : _instance.elementLinkedHashMap.keySet()) {
				CoordinateSet coordinateSet = _instance.elementLinkedHashMap.get( element);

				if ( coordinateSet.getFormulaSet().getLeftFormula() != null) {
					Integer left 
					= coordinateSet.calcLeftFormula(
							bodyWidth, bodyHeight, 
							clientWidth, clientHeight, 
							windowScrollLeft, windowScrollTop, 
							_instance
							);
					if ( left != null) {
						calcFixPositionCoordinateImpl.setPosition( element, AxisOrigin.Left, left);					
					}
				}
				if ( coordinateSet.getFormulaSet().getRightFormula() != null) {
					Integer right 
					= coordinateSet.calcRightFormula(  
							bodyWidth, bodyHeight, 
							clientWidth, clientHeight, 
							windowScrollLeft, windowScrollTop, 
							_instance
							);
					if ( right != null) {
						calcFixPositionCoordinateImpl.setPosition( element, AxisOrigin.Right, right);					
					}
				}
				
				if ( coordinateSet.getFormulaSet().getTopFormula() != null) {
					Integer top 
					= coordinateSet.calcTopFormula(  
							bodyWidth, bodyHeight, 
							clientWidth, clientHeight, 
							windowScrollLeft, windowScrollTop, 
							_instance
							);
					if ( top != null) {
						calcFixPositionCoordinateImpl.setPosition( element, AxisOrigin.Top, top);					
					}
				}
				if ( coordinateSet.getFormulaSet().getBottomFormula() != null) {
					Integer bottom 
					= coordinateSet.calcTopFormula(
							bodyWidth, bodyHeight, 
							clientWidth, clientHeight, 
							windowScrollLeft, windowScrollTop, 
							_instance
							);
					if ( bottom != null) {
						calcFixPositionCoordinateImpl.setPosition( element, AxisOrigin.Bottom, bottom);					
					}
				}
				
			} //for
			
		} // public void onResize(ResizeEvent event)
	} // protected class WindowResizeHandler implements ResizeHandler
	
	protected WindowScrollHandler windowScrollHandler = new WindowScrollHandler();
	protected WindowResizeHandler windowResizeHandler = new WindowResizeHandler();
	
	protected FixPositionInClientWindow() {
		Window.addWindowScrollHandler( windowScrollHandler);
		Window.addResizeHandler( windowResizeHandler);
	}

/*
	public static enum NewScaleOriginType {
		Absolute( 0), Proportion( 1), StayCloserEdge( 2); 
		protected int code;
		private NewScaleOriginType( int code) {
			this.code = code;
		}
		
		public NewScaleOriginType getNewScaleOriginType( int code) {
			for( NewScaleOriginType newScaleOriginType : NewScaleOriginType.values()) {
				if ( newScaleOriginType.code == code) return newScaleOriginType;
			} // for
			return null;
		}
	} // public static enum NewScaleOriginType
	
	public static enum RelationToNewScaleOrigin {
		Absolute( 0), Proportion( 1);
		protected int code;
		private RelationToNewScaleOrigin( int code) {
			this.code = code;
		}
		
		public RelationToNewScaleOrigin getRelationToNewScaleOrigin( int code) {
			for( RelationToNewScaleOrigin relationToNewScaleOrigin : RelationToNewScaleOrigin.values()) {
				if ( relationToNewScaleOrigin.code == code) return relationToNewScaleOrigin;
			} // for
			return null;
		}
	} // public static enum RelationToNewScaleOrigin
*/
	
	public static enum AxisOrigin {
		Left, Right, Top, Bottom;
	}
	
	protected class FactoredFormula {
		protected AxisOrigin axisOrigin;
		public AxisOrigin getAxisOrigin() {
			return axisOrigin;
		}
		protected String formula;
		public String getFormula() {
			return formula;
		}

		public FactoredFormula( AxisOrigin axisOrigin, String formula) {
			this.axisOrigin = axisOrigin;
			this.formula = formula;
		}
	}
	protected class FormulaSet {
		protected String leftFormula = null;
		public String getLeftFormula() {
			return leftFormula;
		}
		protected String rightFormula = null;
		public String getRightFormula() {
			return rightFormula;
		}
		protected String topFormula = null;
		public String getTopFormula() {
			return topFormula;
		}
		protected String bottomFormula = null;
		public String getBottomFormula() {
			return bottomFormula;
		}
		
		public FormulaSet( 
				FactoredFormula factoredLeftFormula, FactoredFormula factoredRightFormula, 
				FactoredFormula factoredTopFormula, FactoredFormula factoredBottomFormula
				) 
		{
			if ( factoredLeftFormula != null) {
				if ( AxisOrigin.Left.equals( factoredLeftFormula.getAxisOrigin())) {
					leftFormula = factoredLeftFormula.getFormula();
GWT.log( "leftFormula = " + leftFormula, null);
				}
			}
			if ( factoredRightFormula != null) {
				if ( AxisOrigin.Right.equals( factoredRightFormula.getAxisOrigin())) {
					rightFormula = factoredRightFormula.getFormula();
GWT.log( "rightFormula = " + rightFormula, null);
				}
			}
			if ( factoredTopFormula != null) {
				if ( AxisOrigin.Top.equals( factoredTopFormula.getAxisOrigin())) {
					topFormula = factoredTopFormula.getFormula();
GWT.log( "topFormula = " + topFormula, null);
				}
			}
			if ( factoredBottomFormula != null) {
				if ( AxisOrigin.Bottom.equals( factoredBottomFormula.getAxisOrigin())) {
					bottomFormula = factoredBottomFormula.getFormula();
GWT.log( "bottomFormula = " + bottomFormula, null);
				}
			}
		}
	} // protected class FormulaSet
	
	protected CalcFixPositionCoordinateImpl calcFixPositionCoordinateImpl = 
		GWT.create( CalcFixPositionCoordinateImpl.class);
	
	protected class CoordinateSet {
		/*
		protected Integer centerX = 0;
		public Integer getCenterX() {
			return centerX;
		}
		protected Integer centerY = 0;
		public Integer getCenterY() {
			return centerY;
		}
		
		protected NewScaleOriginType newScaleOriginType;
		
		protected int originalAbsoluteLeft = -1;
		public int getOriginalAbsoluteLeft() {
			return originalAbsoluteLeft;
		}
		protected int originalAbsoluteTop = -1;
		public int getOriginalAbsoluteTop() {
			return originalAbsoluteTop;
		}
		
		protected int trackedWindowWidth = 0;
		public int getTrackedWindowWidth() {
			return trackedWindowWidth;
		}
		protected int trackedWindowHeight = 0;
		public int getTrackedWindowHeight() {
			return trackedWindowHeight;
		}
		*/
		
/*		protected int trackedWindowScrollLeft = 0;
		public int getTrackedWindowScrollLeft() {
			return trackedWindowScrollLeft;
		}
		protected int trackedWindowScrollTop = 0;
		public int getTrackedWindowScrollTop() {
			return trackedWindowScrollTop;
		}
		
		protected boolean xIgnored = false;
		public boolean isXIgnored() {
			return xIgnored;
		}
		protected boolean yIgnored = false;
		public boolean isYIgnored() {
			return yIgnored;
		}
*/		
		protected FormulaSet formulaSet = null;
		public FormulaSet getFormulaSet() {
			return formulaSet;
		}
		protected String getLeftFormula() {
			if ( formulaSet == null) return null;
			return formulaSet.getLeftFormula();
		}
		protected String getRightFormula() {
			if ( formulaSet == null) return null;
			return formulaSet.getRightFormula();
		}
		protected String getTopFormula() {
			if ( formulaSet == null) return null;
			return formulaSet.getTopFormula();
		}
		protected String getBottomFormula() {
			if ( formulaSet == null) return null;
			return formulaSet.getBottomFormula();
		}
		/*
		 * Before this method is called, value being returned by formulaSet.getLeftFormula() should 
		 * be string evaluated by evaluateXFormula before, and never be either null or empty string
		 */
		private native final Integer calcLeftFormula( 
				int w, int h, int cw, int ch, int sl, int st, FixPositionInClientWindow instance) 
		throws JavaScriptException /*-{
			var xFormula 
			= instance.@com.inetools.gwetons.client.FixPositionInClientWindow$CoordinateSet::getLeftFormula()();
			if ( xFormula == null) return null;
			var xFormulaCopy = xFormula;
			do {
				xFormula = xFormulaCopy.replace( "sl", sl);
				xFormulaCopy = xFormula.replace( "st", st);
				xFormula = xFormulaCopy.replace( "cw", cw);
				xFormulaCopy = xFormula.replace( "ch", ch);
				xFormula = xFormulaCopy.replace( "w", w);
				xFormulaCopy = xFormula.replace( "h", h);
			} while( xFormula != xFormulaCopy);
			
			var calculatedValue = eval( xFormula);
			if ( isNaN( calculatedValue)) return null;
			calculatedValue = parseInt( String( calculatedValue));
			
			return @java.lang.Integer::valueOf(I)( calculatedValue);
		}-*/;
		/*
		 * Before this method is called, value being returned by formulaSet.getRightFormula() should 
		 * be string evaluated by evaluateXFormula before, and never be either null or empty string
		 */
		private native final Integer calcRightFormula( 
				int w, int h, int cw, int ch, int sl, int st, FixPositionInClientWindow instance) 
		throws JavaScriptException /*-{
			var xFormula 
			= instance.@com.inetools.gwetons.client.FixPositionInClientWindow$CoordinateSet::getRightFormula()();
			if ( xFormula == null) return null;
			var xFormulaCopy = xFormula;
			do {
				xFormula = xFormulaCopy.replace( "sl", sl);
				xFormulaCopy = xFormula.replace( "st", st);
				xFormula = xFormulaCopy.replace( "cw", cw);
				xFormulaCopy = xFormula.replace( "ch", ch);
				xFormula = xFormulaCopy.replace( "w", w);
				xFormulaCopy = xFormula.replace( "h", h);
			} while( xFormula != xFormulaCopy);
			
			var calculatedValue = eval( xFormula);
			if ( isNaN( calculatedValue)) return null;
			calculatedValue = parseInt( String( calculatedValue));
			
			return @java.lang.Integer::valueOf(I)( calculatedValue);
		}-*/;

		/*
		 * Before this method is called, value being returned by formulaSet.getTopFormula() should 
		 * be string evaluated by evaluateXFormula before, and never be either null or empty string
		 */
		private native final Integer calcTopFormula( 
				int w, int h, int cw, int ch, int sl, int st, FixPositionInClientWindow instance) 
		throws JavaScriptException /*-{
			var yFormula 
			= instance.@com.inetools.gwetons.client.FixPositionInClientWindow$CoordinateSet::getTopFormula()();
			if ( yFormula == null) return null;
			var yFormulaCopy = yFormula;
			do {
				yFormula = yFormulaCopy.replace( "sl", sl);
				yFormulaCopy = yFormula.replace( "st", st);
				yFormula = yFormulaCopy.replace( "cw", cw);
				yFormulaCopy = yFormula.replace( "ch", ch);
				yFormula = yFormulaCopy.replace( "w", w);
				yFormulaCopy = yFormula.replace( "h", h);
			} while( yFormula != yFormulaCopy);
			
			var calculatedValue = eval( yFormula);
			if ( isNaN( calculatedValue)) return null;
			calculatedValue = parseInt( String( calculatedValue));
			
			return @java.lang.Integer::valueOf(I)( calculatedValue);
		}-*/;
		/*
		 * Before this method is called, value being returned by formulaSet.getBottomFormula() should 
		 * be string evaluated by evaluateXFormula before, and never be either null or empty string
		 */
		private native final Integer calcBottomFormula( 
				int w, int h, int cw, int ch, int sl, int st, FixPositionInClientWindow instance) 
		throws JavaScriptException /*-{
			var yFormula 
			= instance.@com.inetools.gwetons.client.FixPositionInClientWindow$CoordinateSet::getBottomFormula()();
			if ( yFormula == null) return null;
			var yFormulaCopy = yFormula;
			do {
				yFormula = yFormulaCopy.replace( "sl", sl);
				yFormulaCopy = yFormula.replace( "st", st);
				yFormula = yFormulaCopy.replace( "cw", cw);
				yFormulaCopy = yFormula.replace( "ch", ch);
				yFormula = yFormulaCopy.replace( "w", w);
				yFormulaCopy = yFormula.replace( "h", h);
			} while( yFormula != yFormulaCopy);
			
			var calculatedValue = eval( yFormula);
			if ( isNaN( calculatedValue)) return null;
			calculatedValue = parseInt( String( calculatedValue));
			
			return @java.lang.Integer::valueOf(I)( calculatedValue);
		}-*/;

		protected String originalPositionCssRuleStyle = null;
		public String getOriginalPositionCssRuleStyle() {
			return originalPositionCssRuleStyle;
		}
		protected String originalLeftCssRuleStyle = null;
		public String getOriginalLeftCssRuleStyle() {
			return originalLeftCssRuleStyle;
		}
		protected String originalRightCssRuleStyle = null;
		public String getOriginalRightCssRuleStyle() {
			return originalRightCssRuleStyle;
		}
		protected String originalTopCssRuleStyle = null;
		public String getOriginalTopCssRuleStyle() {
			return originalTopCssRuleStyle;
		}
		protected String originalBottomCssRuleStyle = null;
		public String getOriginalBottomCssRuleStyle() {
			return originalBottomCssRuleStyle;
		}

		protected Style elementStyle = null;
		public Style getElementStyle() {
			return elementStyle;
		}
		
		/*
		 * Either factoredXFormula or factoredYFormula is not null
		 */
		public CoordinateSet( Element element, FormulaSet formulaSet)
		{
			this.formulaSet = formulaSet;
			
			int windowScrollLeft = Window.getScrollLeft();
			int windowScrollTop = Window.getScrollTop();
	
			elementStyle = element.getStyle();
			
			originalPositionCssRuleStyle = elementStyle.getProperty( "position");
			if ( originalPositionCssRuleStyle == null) originalPositionCssRuleStyle = "static";
			
			// No matter what factoredXFormula and factoredYFormula are, need to save origianl coordination info
			originalLeftCssRuleStyle = elementStyle.getProperty( "left");
			originalRightCssRuleStyle = elementStyle.getProperty( "right");
			originalTopCssRuleStyle = elementStyle.getProperty( "top");
			originalBottomCssRuleStyle = elementStyle.getProperty( "bottom");

//dumpCoordiateData();			
			
			int bodyWidth = RootPanel.getBodyElement().getOffsetWidth();
			int bodyHeight = RootPanel.getBodyElement().getOffsetHeight();
			
			int windowWidth = Window.getClientWidth();
			int windowHeight = Window.getClientHeight();
			if ( formulaSet.getLeftFormula() != null) {
				Integer left 
				= calcLeftFormula( 
						bodyWidth, bodyHeight, 
						windowWidth, windowHeight, 
						windowScrollLeft, windowScrollTop, 
						_instance
						);
				if ( left != null) {
					calcFixPositionCoordinateImpl.setPosition(element, AxisOrigin.Left, left);
				}
			}
			if ( formulaSet.getRightFormula() != null) {
				Integer right 
				= calcRightFormula( 
						bodyWidth, bodyHeight, 
						windowWidth, windowHeight, 
						windowScrollLeft, windowScrollTop, 
						_instance
						);
				if ( right != null) {
					calcFixPositionCoordinateImpl.setPosition( element, AxisOrigin.Right, right);
				}
			}
			
			if ( formulaSet.getTopFormula() != null) {
				Integer top 
				= calcTopFormula( 
						bodyWidth, bodyHeight, 
						windowWidth, windowHeight, 
						windowScrollLeft, windowScrollTop, 
						_instance
						);
				if ( top != null) {
					calcFixPositionCoordinateImpl.setPosition(element, AxisOrigin.Top, top);
				}
			}
			if ( formulaSet.getBottomFormula() != null) {
				Integer bottom 
				= calcBottomFormula( 
						bodyWidth, bodyHeight, 
						windowWidth, windowHeight, 
						windowScrollLeft, windowScrollLeft, 
						_instance
						);
				if ( bottom != null) {
					calcFixPositionCoordinateImpl.setPosition(element, AxisOrigin.Bottom, bottom);
				}
			}

		} // public CoordinateSet( Element element, boolean ignoreX, boolean ignoreY)
		
	} // protected class CoordinateSet
	
	protected LinkedHashMap<Element, CoordinateSet> elementLinkedHashMap = 
		new LinkedHashMap<Element, CoordinateSet>();
	
	static public boolean releaseElement( Element element) {
		CoordinateSet coordinateSet = _instance.elementLinkedHashMap.remove( element);
		if ( coordinateSet == null) return false;
		Style elementStyle = coordinateSet.getElementStyle();
		
		elementStyle.setProperty( "position", coordinateSet.getOriginalPositionCssRuleStyle());
		
		String originalCssRuleStyle = coordinateSet.getOriginalLeftCssRuleStyle();
		if ( ( originalCssRuleStyle != null) && !"".equals( originalCssRuleStyle)) 
		{
			elementStyle.setProperty( "left", originalCssRuleStyle);
		}
		originalCssRuleStyle = coordinateSet.getOriginalRightCssRuleStyle();
		if ( ( originalCssRuleStyle != null) && !"".equals( originalCssRuleStyle)) 
		{
			elementStyle.setProperty( "right", originalCssRuleStyle);
		}
		
		originalCssRuleStyle = coordinateSet.getOriginalTopCssRuleStyle();
		if ( ( originalCssRuleStyle != null) && !"".equals( originalCssRuleStyle)) 
		{
			elementStyle.setProperty( "top", originalCssRuleStyle);
		}
		originalCssRuleStyle = coordinateSet.getOriginalBottomCssRuleStyle();
		if ( ( originalCssRuleStyle != null) && !"".equals( originalCssRuleStyle)) 
		{
			elementStyle.setProperty( "bottom", originalCssRuleStyle);
		}
		
		return true;
	} // public boolean releaseElement( Element element)

	
	protected static String evaluateFormula( String formula) {
		if ( formula == null) return null;
		String formulaCopy = formula.trim();
		if ( "".equals( formulaCopy)) return null;
		
		formulaCopy = formulaCopy.replace( " ", "");
		formulaCopy = formulaCopy.toLowerCase();
		
		// Check whether all characters except 0-9, ., +, -, *, %, /, (, ) are either w, h, cw, or ch
		String rightContext = formulaCopy;
		RegExpJsniIntf illegalFormulaRegExp 
		= new RegExpJsni( "[^0-9\\.\\+\\-\\*%/\\(\\)]+", false, true, false); 
		while( illegalFormulaRegExp.test( rightContext)) {
			String lastMatch = illegalFormulaRegExp.getLastMatch();
			if ( lastMatch.length() > 2) return null;
			if ( lastMatch.length() < 2) {
				if ( !"w".equalsIgnoreCase( lastMatch) && !"h".equalsIgnoreCase( lastMatch)) return null;
			}
			else {
				if ( 
						!"cw".equalsIgnoreCase( lastMatch) && 
						!"ch".equalsIgnoreCase( lastMatch) &&
						!"sl".equalsIgnoreCase( lastMatch) && 
						!"st".equalsIgnoreCase( lastMatch) 
						) 
				{
					return null;
				}
			}
			rightContext = illegalFormulaRegExp.getRightContext();
		} // while
		
		// Check validity of preceding character of either w, cw, h, ch, sl, st
		illegalFormulaRegExp 
		= new RegExpJsni( "[0-9\\.\\)](w|cw|h|ch|sl|st)", true, true, false);
		if ( illegalFormulaRegExp.test( formulaCopy)) return null;
		
		// Check validity of following character of either w, cw, h, ch, sl, st
		illegalFormulaRegExp 
		= new RegExpJsni( "(w|cw|h|ch|sl|st)[0-9\\.\\(]", true, true, false);
		if ( illegalFormulaRegExp.test( formulaCopy)) return null;
		
		// Check whether fomula starts invalid character or ends invalid character
		illegalFormulaRegExp 
		= new RegExpJsni( "(^[\\.\\+\\-\\*%/])|([\\.\\+\\-\\*%/]$)", false, false, false); 
		if ( illegalFormulaRegExp.test( formulaCopy)) return null;
		
		// Check whether open parenthesis is followed by invalid character or 
		// closing parenthesis is preceded by invalid character
		illegalFormulaRegExp 
		= new RegExpJsni( "(\\([\\.\\+\\-\\*%/\\)])|([\\.\\+\\-\\*%/\\(]\\))", false, false, false); 
		if ( illegalFormulaRegExp.test( formulaCopy)) return null;

		// Check whether pattern of operation character followed by operation character exists
		illegalFormulaRegExp 
		= new RegExpJsni( "[\\.\\+\\-\\*%/]{2,}", false, false, false); 
		if ( illegalFormulaRegExp.test( formulaCopy)) return null;
		
		// Check consistency of parenthesis characters
		RegExpJsniIntf parenthesisRegExp 
		= new RegExpJsni( "\\(|\\)", false, true, false); 
		ArrayList<Integer> openParenthesisIndexArrayList = new ArrayList<Integer>();
		while( parenthesisRegExp.test( formulaCopy)) {
			if ( "(".equals( parenthesisRegExp.getLastMatch())) {
				openParenthesisIndexArrayList.add( parenthesisRegExp.getLastIndex());
			}
			else {
				if ( openParenthesisIndexArrayList.size() < 1) return null;
				openParenthesisIndexArrayList.remove( openParenthesisIndexArrayList.size() - 1);
			}
		} // while
		if ( openParenthesisIndexArrayList.size() > 0) return null;
		
		return formulaCopy;
	} // protected static String evaluateFormula( String formula)

	protected static FactoredFormula evaluateXFormula( String formula) {
		if ( formula == null) return null;
		String formulaCopy = formula.trim();
		if ( "".equals( formulaCopy)) return null;
		
		formulaCopy = formulaCopy.replace( " ", "");
		formulaCopy = formulaCopy.toLowerCase();
		
		RegExpJsniIntf formulaPrefixRegExp = new RegExpJsni( "^[lr]\\:", true, false, false);
		if ( !formulaPrefixRegExp.test( formulaCopy)) return null;
		String leftContext = formulaPrefixRegExp.getLastMatch();
		String rightContext = evaluateFormula( formulaPrefixRegExp.getRightContext());
		if ( rightContext == null) return null;
		
		if ( "l:".equalsIgnoreCase( leftContext)) 
			return _instance.new FactoredFormula( AxisOrigin.Left, rightContext);
		return _instance.new FactoredFormula( AxisOrigin.Right, rightContext);
	} // protected String evaluateXFormula( String formula)

	protected static FactoredFormula evaluateYFormula( String formula) {
		if ( formula == null) return null;
		String formulaCopy = formula.trim();
		if ( "".equals( formulaCopy)) return null;
		
		formulaCopy = formulaCopy.replace( " ", "");
		formulaCopy = formulaCopy.toLowerCase();
		
		RegExpJsniIntf formulaPrefixRegExp = new RegExpJsni( "^[tb]\\:", true, false, false);
		if ( !formulaPrefixRegExp.test( formulaCopy)) return null;
		String leftContext = formulaPrefixRegExp.getLastMatch();
		String rightContext = evaluateFormula( formulaPrefixRegExp.getRightContext());
		if ( rightContext == null) return null;
		
		if ( "t:".equalsIgnoreCase( leftContext)) 
			return _instance.new FactoredFormula( AxisOrigin.Top, rightContext);
		return _instance.new FactoredFormula( AxisOrigin.Bottom, rightContext);
	} // protected String evaluateYFormula( String formula)
	
	/**
	 * xFromula and yFormula are string of formula to calculate the position value (horizontal 
	 * position by xFromula and vertical position by yFormula) on each axis. Those should be for 
	 * calculation of client window coordinate, not absolute coordinate.
	 * value for xFromula must begin by either "L:" or "R:".
	 * 	L: for left origin coordinate, means left CSS style rule is used for positioning
	 * 	R: for right origin coordinate, means right CSS style rule is used for positioning
	 * value for yFormula must begin by either "T:" or "B:".
	 * 	T: for left origin coordinate, means top CSS style rule is used for positioning
	 * 	B: for right origin coordinate, means bottom CSS style rule is used for positioning
	 * rest of value of those should be consisted of numbers, ., +, -, *, %, /, (), W, H, CW, CH, SL, ST.
	 * 	W means the width of web page. 
	 * 	H means the height of web page. 
	 * 	CW means width of client window. 
	 * 	CH means the height of client window.
	 * 	SL means scroll left.
	 *  ST means scroll top. 
	 * 	All of those value will be automatically grabbed. 
	 * When xForumla or yFormula is either null, empty string or invalid, then setting for the corresponding axis will be 
	 * ignored, means target element will not be fixed and scroll on that axis. If any other character or 
	 * illegality as formula is found, then also setting for the corresponding axis will be ignored.
	 * Don't allow number begin by '.' character: if you like to use decimal point, then you need to 
	 * precede it by number character.
	 * For example: 
	 * 	0
	 *  CW/2
	 *  CW-20
	 * @param element
	 * @param ignoreX
	 * @param ignoreY
	 * @param xFromula
	 * @param yFormula
	 */
	/*
	 * ignoreX and ignoreY are to set scrollable axis. For example, if position:fix is used, then element 
	 * won't scroll neither x axis or y axis. When ignoreX set to true, then element will be scrolled side 
	 * way. When ignoreY set to true, then element will be scrolled vertical way. When both of ignoreX and 
	 * ignoreY set to true, then element's position will be changed back to normal.  
	 * 
	 * Usually coordinate is measured from the scale origin fixed at left top corner as (0,0).
	 * newScaleOriginX and newScaleOrignY are for changing that. So, if you give (320, 280) as 
	 * newScaleOriginX and newScaleOrignY, then (320, 280) becomes new origin of scale: (0, 0).
	 * 
	 * newScaleOriginType specify how new origin of scale will be treated when size of 
	 * client window changes:
	 * 	Absolute: 
	 * 		Even client window size is changed, the coordinate of scale origin will not change from 
	 * 		(320, 280) 
	 * 	Proportion: 
	 * 		When this method is called, let's say that the size of client window was (480, 350). Later, 
	 * 		if the size of client window is enlarged to (800, 600), then the coordinate of scale origin 
	 * 		becomes as (533, 480).
	 * 	StayCloserEdge
	 *  	When this method is called, let's say that the size of client window was (480, 350). Later,
	 * 		if the size of client window is enlarged to (800, 600), then the coordinate of scale origin 
	 * 		will keep the distances to closer edges which are 160 to right and 70 to bottom. Hence, the 
	 * 		new coordinate of scale of origin becomes (640, 530).
	 * 		When the coordinate of scale origin points at center, then, even StayCloserEdge is specified, 
	 * 		newScaleOriginType will be changed to Proportion automatically.
	 *  
	 *  relationToNewScaleOrigin specifies the relation between element and the coordinate of scale origin. 
	 *  	Absolute: distance to the coordinate of scale origin will be kept fixed no matter to 
	 *  	size change event of client window.
	 *  	Proportion: distance to the coordinate of scale origin will be calculated according to 
	 *  	size change of client Window.
	 *  
	 * @param element
	 * @param ignoreX
	 * @param ignoreY
	 * @param newScaleOriginType
	 * @param newScaleOriginX
	 * @param newScaleOrignY
	 * @param relationToNewScaleOrigin
	 */
	public static void fixPosition( 
			Element element, String lFormula, String rFormula, String tFormula, String bFormula) 
	{
		if ( element == null) return;
		if ( element.getNodeType() != Element.ELEMENT_NODE) return;
//		Element bodyElement = RootPanel.getBodyElement();
//		if ( element.equals( bodyElement)) return;

		FactoredFormula factoredLeftFormula = evaluateXFormula( lFormula);
		FactoredFormula factoredRightFormula = evaluateXFormula( rFormula);
		FactoredFormula factoredTopFormula = evaluateYFormula( tFormula);
		FactoredFormula factoredBottomFormula = evaluateYFormula( bFormula);
		
		FormulaSet formulaSet = 
			_instance.new FormulaSet( 
					factoredLeftFormula, factoredRightFormula, factoredTopFormula, factoredBottomFormula);
		
		if ( 
				( formulaSet.getLeftFormula() == null) && 
				( formulaSet.getRightFormula() == null) && 
				( formulaSet.getTopFormula() == null) && 
				( formulaSet.getBottomFormula() == null)
				) return;

		CoordinateSet coordinateSet = _instance.new CoordinateSet( element, formulaSet);
		
		//Avoid just to replace. Add logic to preserve the coordinate info in coordinateSet when 
		// element already exists in  elementLinkedHashMap. Otherwise, original coordinate info will be lost.
		if ( _instance.elementLinkedHashMap.containsKey( element)) {
			CoordinateSet storedCoordinateSet = _instance.elementLinkedHashMap.get( element);
			coordinateSet.originalLeftCssRuleStyle = storedCoordinateSet.originalLeftCssRuleStyle;
			coordinateSet.originalRightCssRuleStyle = storedCoordinateSet.originalRightCssRuleStyle;
			coordinateSet.originalTopCssRuleStyle = storedCoordinateSet.originalTopCssRuleStyle;
			coordinateSet.originalBottomCssRuleStyle = storedCoordinateSet.originalBottomCssRuleStyle;
		}
		_instance.elementLinkedHashMap.put( element, coordinateSet);
	}
	
}
